package com.atguigu.redis7.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zhumq
 * @date 2024/7/28 21:26
 */
@Service
@Slf4j
public class GeoService {
    
    public static final String CITY = "city";
    @Resource
    private RedisTemplate redisTemplate;
    
    /**
     * 向Redis中的地理信息索引添加多个位置点。
     * 此方法用于将指定的地点及其坐标添加到Redis的地理信息系统中，以便进行地理位置相关的查询和操作。
     *
     * @return 返回添加的地点及其坐标的Map的字符串表示。
     */
    public String geoAdd() {
        // 创建一个Map来存储地点名称和其对应的坐标点 经纬度
        Map<String, Point> map = new HashMap<>();
        map.put("天安门", new Point(116.403963, 39.915119));
        map.put("故宫", new Point(116.403414, 39.924091));
        map.put("长城", new Point(116.024067, 40.362639));
        
        // 使用RedisTemplate的opsForGeo()方法添加地理位置信息到Redis中
        redisTemplate.opsForGeo().add(CITY, map);
        
        // 返回添加的地点及其坐标的Map的字符串表示
        return map.toString();
    }
    
    
    /**
     * 根据成员名称获取其在地理空间中的位置。
     *
     * 本方法通过Redis的GeoSpatial索引功能，查询指定成员在地理空间中的经度和纬度。
     * 它适用于已经将地理位置信息存储在Redis中的场景，例如存储城市的地理位置。
     *
     * @param member 要查询位置的成员名称。
     * @return 返回一个Point对象，包含该成员的经度和纬度。
     */
    public Point position(String member) {
        // 通过RedisTemplate的opsForGeo()方法获取GeoOperations实例，用于进行地理空间操作
        // 接着调用position方法，查询指定成员(CITY中的member)的位置
        // 获取经纬度坐标
        List<Point> list = redisTemplate.opsForGeo().position(CITY, member);
        // 由于查询结果是一个包含Point对象的List，此处返回List中的第一个元素，即查询到的位置
        return list.get(0);
    }
    
    /**
     * 根据成员名称计算其在地理空间中的哈希值。
     *
     * 该方法用于对给定的成员在其所属的地理空间中进行哈希编码。它利用Redis的geohash算法，
     * 将成员的位置信息转换为一个base32编码的字符串，这个字符串代表了成员在地理空间中的位置。
     * 这种哈希编码可用于地理位置的查询和计算，比如计算成员之间的距离、查找附近成员等。
     *
     * @param member 成员的名称，这个成员是在Redis的地理空间数据结构中注册的。
     * @return 返回成员的geohash编码字符串。如果成员不存在，则返回空字符串。
     */
    public String hash(String member) {
        // 通过redisTemplate的opsForGeo()方法获取geoHash操作对象
        // 然后使用hash()方法计算成员的geohash值，返回一个包含geohash字符串的List
        // geohash算法生成的base32编码值
        List<String> list = redisTemplate.opsForGeo().hash(CITY, member);
        // 由于hash方法返回的是一个List，其中包含成员的经度和纬度的geohash值
        // 这里只返回第一个元素，即成员的纬度的geohash值
        return list.get(0);
    }
    
    
    /**
     * 计算两个成员之间的地理距离。
     *
     * 该方法使用Redis的地理空间索引功能，来计算指定城市中两个成员之间的距离。
     * 距离单位为千米。
     *
     * @param member1 第一个成员的名称。这是在Redis的地理空间索引中的一个位置点。
     * @param member2 第二个成员的名称。这也是在Redis的地理空间索引中的一个位置点。
     * @return 返回两个成员之间的距离。如果其中一个或两个成员不存在，或者距离无法计算，返回值可能为null。
     */
    public Distance distance(String member1, String member2) {
        // 通过redisTemplate的操作接口，计算并返回两个成员之间的距离
        Distance distance = redisTemplate.opsForGeo().distance(CITY, member1, member2,
                RedisGeoCommands.DistanceUnit.KILOMETERS);
        return distance;
    }
    
    /**
     * 根据给定的经度和纬度查询指定地点附近的地理坐标信息。
     *
     * 本方法通过Redis的GeoRadius命令，查询指定地点（这里以北京王府井为例）周围一定范围内的地理坐标信息。
     * 查询结果包括地理位置的距离和坐标，排序方式为由远及近，最多返回50条结果。
     *
     * @return GeoResults对象，包含查询到的地理位置信息。
     */
    public GeoResults radiusByxy() {
        // 创建一个圆，指定中心点为北京王府井，半径为1公里
        // 通过经度，纬度查找附近的,北京王府井位置116.418017,39.914402
        Circle circle = new Circle(116.418017, 39.914402, Metrics.KILOMETERS.getMultiplier());
        // 设置查询参数，包括返回距离、坐标，并按距离降序排序，限制结果数量为50
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortDescending().limit(50);
        // 执行地理距离查询，查询地点为CITY，即指定的圆形区域
        GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = redisTemplate.opsForGeo().radius(CITY, circle, args);
        // 返回查询结果
        return geoResults;
    }
    
    
    /**
     * 根据指定地点和距离查询附近的地理信息。
     *
     * 该方法使用Redis的GEORADIUS命令，以天安门为中心，在5公里的范围内查找地理坐标点。
     * 返回的结果包括距离和坐标，排序方式为从远到近。
     *
     * @return GeoResults对象，包含查询结果的集合。
     */
    public GeoResults radiusByMember() {
        // 设置查询半径为5公里
        Distance distance = new Distance(5, Metrics.KILOMETERS);
        // 配置查询参数，包括按距离降序排序、包含距离和坐标信息
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().sortDescending().includeDistance().includeCoordinates();
        // 执行地理信息查询
        // GeoResults results = redisTemplate.opsForGeo().radius(CITY, "天安门", distance, args);
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo().radius(CITY, "天安门", distance, args);
        // 返回查询结果
        return results;
    }
    
}
